package com.xsf.pay.service;

import cn.hutool.core.util.StrUtil;
import com.xsf.pay.common.domain.CodeMsg;
import com.xsf.pay.common.exception.BusinessRunTimeException;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static java.lang.Thread.sleep;

/**
 * @author xujianfeng
 * @description 分布式锁业务类
 * @date: 2023-04-20
 */
@Service
@Slf4j
public class LockService {

    @Resource
    private RedisService redisService;

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    public <T> T executeWithLockThrows(String key, long waitTime, TimeUnit unit, String message,
                                       SupplierThrow<T> supplier) throws Throwable {
        long expireTime = System.currentTimeMillis() + unit.toMillis(waitTime);
        boolean lockSuccess = redisService.lock(key, String.valueOf(expireTime));
        if (!lockSuccess) {
            throw new BusinessRunTimeException(StrUtil.isNotBlank(message) ? CodeMsg.valueOf(message) : CodeMsg.OFTEN_LOCK_ERROR);
        }
        try {
            //执行锁内的代码逻辑
            return supplier.get();
        } finally {
            redisService.unlock(key, String.valueOf(expireTime));
        }
    }

    /**
     * 循环获取锁
     *
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    public <T> T executeWithLockThrowsLoop(String key, long waitTime, TimeUnit unit, String message,
                                           SupplierThrow<T> supplier) throws Throwable {
        long expireTime = System.currentTimeMillis() + unit.toMillis(waitTime);
        boolean lockSuccess;
        while (true) {
            //判断是否超过了，尝试获取锁的时间
            if (System.currentTimeMillis() > expireTime) {
                throw new BusinessRunTimeException(StrUtil.isNotBlank(message) ? CodeMsg.valueOf(message) : CodeMsg.OFTEN_LOCK_ERROR);
            }
            lockSuccess = redisService.lock(key, String.valueOf(expireTime));
            if (lockSuccess) {
                break;
            }
            //获得锁失败，休眠100毫秒再去尝试获得锁,避免一直请求redis，导致redis cpu飙升
            sleep(100);
        }
        try {
            //执行锁内的代码逻辑
            return supplier.get();
        } finally {
            redisService.unlock(key, String.valueOf(expireTime));
        }
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    @SneakyThrows
    public <T> T executeWithLock(String key, long waitTime, TimeUnit unit, String message, Supplier<T> supplier) {
        return executeWithLockThrows(key, waitTime, unit, message, supplier::get);
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    @SneakyThrows
    public <T> T executeWithLockLoop(String key, long waitTime, TimeUnit unit, String message, Supplier<T> supplier) {
        return executeWithLockThrowsLoop(key, waitTime, unit, message, supplier::get);
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    @SneakyThrows
    public <T> T executeWithLockLoop(String key, long waitTime, TimeUnit unit, Supplier<T> supplier) {
        return executeWithLockThrowsLoop(key, waitTime, unit, null, supplier::get);
    }

    /**
     * @param key      分布式锁主键
     * @param message  提示消息
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    @SneakyThrows
    public <T> T executeWithLock(String key, String message, Supplier<T> supplier) {
        return executeWithLock(key, -1, TimeUnit.MILLISECONDS, message, supplier);
    }

    /**
     * @param key      分布式锁主键
     * @param supplier 指向方法
     * @param <T>      泛型
     * @return <T> 泛型
     */
    @SneakyThrows
    public <T> T executeWithLock(String key, Supplier<T> supplier) {
        return executeWithLock(key, -1, TimeUnit.MILLISECONDS, null, supplier);
    }

    @FunctionalInterface
    public interface SupplierThrow<T> {
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get() throws Throwable;
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param consumer 指向方法
     */
    public void executeNoArgsLockThrows(String key, long waitTime, TimeUnit unit, String message,
                                        ConsumerThrow consumer) throws Throwable {
        long expireTime = System.currentTimeMillis() + unit.toMillis(waitTime);
        boolean lockSuccess = redisService.lock(key, String.valueOf(expireTime));
        if (!lockSuccess) {
            throw new BusinessRunTimeException(StrUtil.isNotBlank(message) ? CodeMsg.valueOf(message) : CodeMsg.OFTEN_LOCK_ERROR);
        }
        try {
            //执行锁内的代码逻辑
            consumer.get();
        } finally {
            redisService.unlock(key, String.valueOf(expireTime));
        }
    }

    /**
     * 循环获取锁
     *
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param consumer 指向方法
     */
    public void executeNoArgsThrowsLoop(String key, long waitTime, TimeUnit unit, String message,
                                        ConsumerThrow consumer) throws Throwable {
        long expireTime = System.currentTimeMillis() + unit.toMillis(waitTime);
        boolean lockSuccess;
        while (true) {
            //判断是否超过了，尝试获取锁的时间
            if (System.currentTimeMillis() > expireTime) {
                throw new BusinessRunTimeException(StrUtil.isNotBlank(message) ? CodeMsg.valueOf(message) : CodeMsg.OFTEN_LOCK_ERROR);
            }
            lockSuccess = redisService.lock(key, String.valueOf(expireTime));
            if (lockSuccess) {
                break;
            }
            //获得锁失败，休眠100毫秒再去尝试获得锁,避免一直请求redis，导致redis cpu飙升
            Thread.sleep(100);
        }
        try {
            //执行锁内的代码逻辑
            consumer.get();
        } finally {
            redisService.unlock(key, String.valueOf(expireTime));
        }
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param consumer 指向方法
     */
    @SneakyThrows
    public void executeNoArgsLock(String key, long waitTime, TimeUnit unit, String message, ConsumerThrow consumer) {
        executeNoArgsLockThrows(key, waitTime, unit, message, consumer);
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param message  提示消息
     * @param consumer 指向方法
     */
    @SneakyThrows
    public void executeNoArgsLockLoop(String key, long waitTime, TimeUnit unit, String message, ConsumerThrow consumer) {
        executeNoArgsThrowsLoop(key, waitTime, unit, message, consumer);
    }

    /**
     * @param key      分布式锁主键
     * @param waitTime 等待时间
     * @param unit     等待时间单位
     * @param consumer 指向方法
     */
    @SneakyThrows
    public void executeNoArgsLockLoop(String key, long waitTime, TimeUnit unit, ConsumerThrow consumer) {
        executeNoArgsThrowsLoop(key, waitTime, unit, null, consumer);
    }

    /**
     * @param key      分布式锁主键
     * @param message  提示消息
     * @param consumer 指向方法
     */
    @SneakyThrows
    public void executeNoArgsLock(String key, String message, ConsumerThrow consumer) {
        executeNoArgsLock(key, -1, TimeUnit.MILLISECONDS, message, consumer);
    }

    /**
     * @param key      分布式锁主键
     * @param consumer 指向方法
     */
    @SneakyThrows
    public void executeNoArgsLock(String key, ConsumerThrow consumer) {
        executeNoArgsLock(key, -1, TimeUnit.MILLISECONDS, null, consumer);
    }

    @FunctionalInterface
    public interface ConsumerThrow {
        /**
         * not args method
         */
        void get() throws Throwable;
    }

}
